package core

import (
	"fmt"
	"goetl/models"
	"goetl/services"
	"goetl/utils"
	"sync"

	"github.com/emirpasic/gods/sets/hashset"

	"github.com/snluu/uuid"
)

type AgentProjectVersionFlowDeployment struct {
	deployment     models.AgentDeployment
	flow           models.Flow
	projectVersion models.ProjectVersion
}

//agent 运行时

type AgentRuntime struct {
	agent models.Agent

	//是否已开始
	started bool

	//是否开始中
	starting bool
	//是否结束中
	stopping bool

	//已部署
	deployed *hashset.Set

	//运行中的流程
	runningFlows map[string]*FlowRuntime
	//读写锁
	sync.RWMutex
}

func NewAgentRuntime(agent models.Agent) *AgentRuntime {
	return &AgentRuntime{started: false, starting: false, stopping: false, agent: agent}
}

func (this *AgentRuntime) isStarted() bool {
	return this.started
}
func (this *AgentRuntime) GetDeployed() *hashset.Set {
	return this.deployed
}
func RunThread(runner *FlowRunner) {
	(*runner).Run()
}

//开启agent
func (this *AgentRuntime) Start() {

	if !this.started && !this.starting {

		//初始化已部署
		this.deployed = hashset.New()
		//初始化运行中流程
		this.runningFlows = make(map[string]*FlowRuntime)

		for _, deployment := range this.agent.AgentDeployments {
			this.DeployByAgentDeployment(*deployment)
		}

		this.RLock()
		this.started = true
		this.starting = false
		this.RUnlock()

	}

}
func (this *AgentRuntime) DeployByAgentDeployment(deployment models.AgentDeployment) {

	err, flow := services.ConfigurationService.FindFlow(deployment.FlowId.Id)
	if err == nil {

		err, projectVersion := services.ConfigurationService.FindProjectVersion(flow.ProjectVersionId.Id)

		if err == nil {

			if projectVersion.Deleted == 0 {

				this.DeployByFlow(deployment, flow, projectVersion)
			} else {
				utils.ConsoleLogs.Info("---\nFailed to deploy the flow '%s' from the project version '%s:%s' because the project or version had been deleted \n----", flow.Name, projectVersion.ProjectId.Name, projectVersion.VersionLabel)
			}
		}
	}
}
func (this *AgentRuntime) DeployByFlow(deployment models.AgentDeployment, flow models.Flow, projectVersion models.ProjectVersion) {

	utils.ConsoleLogs.Info("----\nDeploying '%s' to '%s' \n----", deployment.Name, this.agent.Name)
	agentProjectVersionFlowDeployment := &AgentProjectVersionFlowDeployment{deployment: deployment, flow: flow,
		projectVersion: projectVersion}

	this.RLock()
	this.deployed.Add(agentProjectVersionFlowDeployment)

	this.RUnlock()

	utils.ConsoleLogs.Info("----\nFlow '%s' has been deployed \n-----", deployment.Name)
}

//立刻调度流程
func (this *AgentRuntime) ScheduleNow(userId string, deployment models.AgentDeployment, runtimeParameters map[string]string) string {

	utils.ConsoleLogs.Info("Scheduling '%s' on '%s' for now", deployment.Name, deployment.AgentId.Name)

	executionId := this.createExecutionId()

	runner := &FlowRunner{agentRuntime: this, userId: userId, deployment: this.FindDeployed(deployment), executionId: executionId, runtimeParameters: runtimeParameters}
	go RunThread(runner)

	return executionId
}

//查找已部署
func (this *AgentRuntime) FindDeployed(deployment models.AgentDeployment) AgentProjectVersionFlowDeployment {

	var d AgentProjectVersionFlowDeployment

	if this.deployed != nil {

		for i := range this.deployed.Values() {
			elem := this.deployed.Values()[i]
			if elem.(*AgentProjectVersionFlowDeployment).deployment.Id == deployment.Id {
				d = *(elem.(*AgentProjectVersionFlowDeployment))
			}
		}
	}
	return d

}

//运行execution ID
func (this *AgentRuntime) createExecutionId() string {
	return uuid.Rand().Hex()
}

//流程运行器
type FlowRunner struct {
	agentRuntime      *AgentRuntime
	executionId       string
	userId            string
	runtimeParameters map[string]string
	deployment        AgentProjectVersionFlowDeployment
}

func (this *FlowRunner) Run() {

	utils.ConsoleLogs.Info("executionId =%s", this.executionId)
	fmt.Println(this.deployment.deployment)
	utils.ConsoleLogs.Info("Deployment '%s' is running on the '%s' agent", this.deployment.deployment.Name, this.agentRuntime.agent.Name)

	flowRuntime := NewFlowRuntime(this.executionId, this.userId, this.deployment, *this.deployment.deployment.AgentId)

	flowRuntime.Execute()
}
